/*
 *  Copyright (c) 2022 ZhuHai Jieli Technology Co.,Ltd.
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

#include "asm/cpu.h"
#include "asm/power/p33.h"
#include "asm/adc_api.h"
#include "syscfg_id.h"

#define wvdd_default_level      0b110
#define sysvdd_default_level    0b1101
#define vdc13_default_level     0b110
#define mvddio_default_level    0b110
#define wvddio_default_level    0b10
typedef struct {
    u16 wvdd_voltage[8];
    u16 sysvdd_voltage[16];
    u16 vdc13_voltage[8];
    u16 vddio_voltage[4][8];

    /* adc trim后挡位 */
    u8 vddiom_lev;
    u8 vddiow_lev;
    u8 valid;

} pmu_voltage_type;
static pmu_voltage_type pmu_voltage;

typedef enum {
    closest,        // 最接近目标值，不管偏大还是偏小
    closest_min,    // 最接近目标值，并小于目标值
    closest_max,    // 最接近目标值，并大于目标值
} choose_type;
static u8 get_aims_level(u16 aims, u16 *tmp_buf, u8 len, choose_type choose)
{
    u16 diff;
    u16 min_diff0 = -1;
    u16 min_diff1 = -1;
    u8 closest_min_level = -1;
    u8 closest_max_level = -1;
    for (u8 i = 0; i < len; i ++) {
        if (tmp_buf[i] >= aims) {
            diff = tmp_buf[i] - aims;
            if (diff < min_diff0) {
                min_diff0 = diff;
                closest_max_level = i;
            }
        } else {
            diff = aims - tmp_buf[i];
            if (diff < min_diff1) {
                min_diff1 = diff;
                closest_min_level = i;
            }
        }
    }
    u8 closest_level = -1;
    if (min_diff0 < min_diff1) {
        closest_level = closest_max_level;
    } else {
        closest_level = closest_min_level;
    }
    u8 res_level = -1;
    if (choose == closest_min) {
        res_level = closest_min_level;
    } else if (choose == closest_max) {
        res_level = closest_max_level;
    } else {
        res_level = closest_level;
    }
    return res_level;
}

static u32 get_ch_voltage(u32 ch)
{
    adc_pmu_detect_en(1);
    u32 i;
    u32 tmp_vbg = 0;
    adc_sample(AD_CH_LDOREF);
    for (i = 0; i < 10; i++) {
        while (!(JL_ADC->CON & BIT(7)));
        tmp_vbg += JL_ADC->RES;
        JL_ADC->CON |= BIT(6);
    }
    tmp_vbg /= 10;

    u32 tmp_ch_val = 0;
    adc_sample(ch);
    for (int i = 0; i < 10; i++) {
        while (!(JL_ADC->CON & BIT(7)));
        tmp_ch_val += JL_ADC->RES;
        JL_ADC->CON |= BIT(6);
    }
    tmp_ch_val /= 10;

    return adc_value_to_voltage(tmp_vbg, tmp_ch_val);
}

static void record_wvdd_voltage(void)
{
    u8 old_level = P33_CON_GET(P3_WLDO06_AUTO);
    u8 wldo06_en = P33_CON_GET(P3_ANA_CON1);
    P33_CON_SET(P3_ANA_CON1, 3, 1, 1);
    for (u8 i = 0; i < 8; i++) {
        P33_CON_SET(P3_WLDO06_AUTO, 0, 3, i);
        delay(2000);
        pmu_voltage.wvdd_voltage[i] = get_ch_voltage(AD_CH_WVDD);
    }
    P33_CON_SET(P3_WLDO06_AUTO, 0, 3, old_level);
    P33_CON_SET(P3_ANA_CON1, 3, 1, (wldo06_en >> 3));
}

static void record_sysvdd_voltage(void)
{
    u8 old_level = P33_CON_GET(P3_ANA_CON9);
    for (u8 i = 0; i < 16; i ++) {
        P33_CON_SET(P3_ANA_CON9, 0, 4, i);
        delay(2000);
        pmu_voltage.sysvdd_voltage[i] = get_ch_voltage(AD_CH_SYSVDD);
    }
    P33_CON_SET(P3_ANA_CON9, 0, 4, old_level);
}

static void record_vdc13_voltage(void)
{
    u8 old_level = P33_CON_GET(P3_ANA_CON6);
    for (u8 i = 0; i < 8; i ++) {
        P33_CON_SET(P3_ANA_CON6, 0, 3, i);
        delay(2000);
        pmu_voltage.vdc13_voltage[i] = get_ch_voltage(AD_CH_VDC13);
    }
    P33_CON_SET(P3_ANA_CON6, 0, 3, old_level);
}

AT(.volatile_ram_code) static void record_vddio_voltage(void)
{
    u32 vbg_center = adc_value_to_voltage(1, 1);
    u8 old_vddio_level = P33_CON_GET(P3_ANA_CON5);
    u8 old_vlvd_level = P33_CON_GET(P3_VLVD_CON);
    P33_CON_SET(P3_VLVD_CON, 6, 1, 1);  // clr_pend
    P33_CON_SET(P3_VLVD_CON, 0, 1, 0);  // 关掉LVD
    adc_pmu_detect_en(1);
    adc_sample(AD_CH_LDOREF);

    local_irq_disable();
    while (!(JL_DSP->CON & BIT(5)));        // while cache idle
    while (JL_SFC->CON & BIT(31));          // while sfc idle

    u32 tmp_cnt;
    for (u8 j = 0; j < 4; j ++) {
        P33_CON_SET(P3_ANA_CON5, 4, 2, j);
        tmp_cnt = 2000;
        while (tmp_cnt --) {
            asm("nop");
        }
        for (u8 i = 0; i < 8; i ++) {
            P33_CON_SET(P3_ANA_CON5, 0, 3, i);
            tmp_cnt = 2000;
            while (tmp_cnt --) {
                asm("nop");
            }
            JL_ADC->CON |= BIT(6);
            while (!(JL_ADC->CON & BIT(7)));
            tmp_cnt = JL_ADC->RES;
            pmu_voltage.vddio_voltage[j][i] = vbg_center * 1024 / tmp_cnt;
        }
    }
    P33_CON_SET(P3_ANA_CON5, 0, 3, old_vddio_level);
    P33_CON_SET(P3_ANA_CON5, 4, 2, (old_vddio_level >> 4));
    tmp_cnt = 2000;
    while (tmp_cnt --) {
        asm("nop");
    }
    P33_CON_SET(P3_VLVD_CON, 0, 1, old_vlvd_level);

    local_irq_enable();
}

u8 get_wvdd_aims_level(u16 aims_mv)
{
    if (pmu_voltage.wvdd_voltage[0] == 0) {
        return wvdd_default_level;
    }
    return get_aims_level(aims_mv, pmu_voltage.wvdd_voltage, 8, closest_max);
}

u8 get_sysvdd_aims_level(u16 aims_mv)
{
    if (pmu_voltage.sysvdd_voltage[0] == 0) {
        return sysvdd_default_level;
    }
    return get_aims_level(aims_mv, pmu_voltage.sysvdd_voltage, 16, closest_max);
}

u8 get_vdc13_aims_level(u16 aims_mv)
{
    if (pmu_voltage.vdc13_voltage[0] == 0) {
        return vdc13_default_level;
    }
    return get_aims_level(aims_mv, pmu_voltage.vdc13_voltage, 8, closest_max);
}

u8 get_mvddio_aims_level(u16 aims_mv)
{
    if (pmu_voltage.vddio_voltage[0][0] == 0) {
        return mvddio_default_level;
    }
    u8 wvddio_level = ((P33_CON_GET(P3_ANA_CON5)) >> 4) & 0b11;
    return get_aims_level(aims_mv, pmu_voltage.vddio_voltage[wvddio_level], 8, closest_max);
}
u8 get_wvddio_aims_level(u16 aims_mv)
{
    if (pmu_voltage.vddio_voltage[0][0] == 0) {
        return wvddio_default_level;
    }
    u8 mvddio_level = (P33_CON_GET(P3_ANA_CON5)) & 0b111;
    u16 tmp_buf[4] = {0};
    tmp_buf[0] = pmu_voltage.vddio_voltage[0][mvddio_level];
    tmp_buf[1] = pmu_voltage.vddio_voltage[1][mvddio_level];
    tmp_buf[2] = pmu_voltage.vddio_voltage[2][mvddio_level];
    tmp_buf[3] = pmu_voltage.vddio_voltage[3][mvddio_level];
    return get_aims_level(aims_mv, tmp_buf, 4, closest);
}

void pmu_voltage_dump(void)
{
    for (int i = 0; i < ARRAY_SIZE(pmu_voltage.sysvdd_voltage); i++) {
        log_i("sysvdd_voltage[%d] %d", i, pmu_voltage.sysvdd_voltage[i]);
    }

    for (int i = 0; i < ARRAY_SIZE(pmu_voltage.vdc13_voltage); i++) {
        log_i("vdc13_voltage[%d] %d", i, pmu_voltage.vdc13_voltage[i]);
    }

    u8 wvddio_level = ((P33_CON_GET(P3_ANA_CON5)) >> 4) & 0b11;
    for (int i = 0; i < ARRAY_SIZE(pmu_voltage.vddio_voltage[wvddio_level]); i++) {
        log_i("mvddio_voltage[%d] %d", i, pmu_voltage.vddio_voltage[wvddio_level][i]);
    }
}

void check_pmu_voltage(u8 tieup)
{
    memset_s((u8 *)&pmu_voltage, sizeof(pmu_voltage), 0, sizeof(pmu_voltage));
    int vm_len = syscfg_read(VM_PMU_VOLTAGE, (u8 *)&pmu_voltage, sizeof(pmu_voltage_type));
    if (vm_len <= 0) {
        log_i("vm read pmu voltage faild\n");
        record_wvdd_voltage();
        record_sysvdd_voltage();
        record_vdc13_voltage();
        u32 tmp_vbat = 4 * get_ch_voltage(AD_CH_VBAT);
        u32 tmp_ldo5v = 4 * get_ch_voltage(AD_CH_LDO5V);
        if (((tmp_vbat >= 3700) || (tmp_ldo5v >= 3700)) && (!tieup)) {
            record_vddio_voltage();
        } else {
            log_i("record vddio voltage faild: tieup = %d || vbat = %dmv < 3700mv || ldo5v = %dmv < 3700mv\n", tieup, \
                tmp_vbat, tmp_ldo5v);
        }
        vm_len = syscfg_write(VM_PMU_VOLTAGE, (u8 *)&pmu_voltage, sizeof(pmu_voltage_type));
        if (vm_len <= 0) {
            log_i("vm write pmu voltage faild\n");
        }
    }
}

/* ----------------------for lcd powerdown, trim vddiow = vddiom = 3.0V-------------------------------- */
char get_vddiom_trim(void)
{
    if (pmu_voltage.valid == 1) {
        return pmu_voltage.vddiom_lev;
    } else {
        return -1;
    }
}

char get_vddiow_trim(void)
{
    if (pmu_voltage.valid == 1) {
        return pmu_voltage.vddiow_lev;
    } else {
        return -1;
    }
}

AT(.volatile_ram_code) static void delay32(u32 cnt)
{
    while (cnt--) {
        asm("nop");
    }
}

#define abs(x)  ((x) > 0 ? (x) : -(x))

AT(.volatile_ram_code) static void __vddio_trim_30V(u16 vddiom_limit)
{
    u32 vbg_center = adc_value_to_voltage(1, 1);
    u8 old_vlvd_level = P33_CON_GET(P3_VLVD_CON);
    P33_CON_SET(P3_VLVD_CON, 6, 1, 1);  // clr_pend
    P33_CON_SET(P3_VLVD_CON, 0, 1, 0);  // 关掉LVD
    adc_pmu_detect_en(1);
    adc_sample(AD_CH_LDOREF);

    local_irq_disable();
    while (!(JL_DSP->CON & BIT(5)));        // while cache idle
    while (JL_SFC->CON & BIT(31));          // while sfc idle

    u16 vddiow_3, vddiow_2, vddiow_val, vddiom_val1, vddiom_val2, vddiom_val3, vddiom_val, tmp_cnt;

    /* =============vddiow trim======================== */
    /* 1.wvddio = 3, mvddio=0, 采vddio的值 */
    VDDIOW_VOL_SEL(3);
    delay32(2000);
    VDDIOM_VOL_SEL(0);
    delay32(2000);
    JL_ADC->CON |= BIT(6);
    while (!(JL_ADC->CON & BIT(7)));
    tmp_cnt = JL_ADC->RES;
    vddiow_3 = ((u32)vbg_center * 1023) / tmp_cnt;

    /* 2.wvddio = 2, mvddio=0, 采vddio的值 */
    VDDIOW_VOL_SEL(2);
    delay32(2000);
    JL_ADC->CON |= BIT(6);
    while (!(JL_ADC->CON & BIT(7)));
    tmp_cnt = JL_ADC->RES;
    vddiow_2 = ((u32)vbg_center * 1023) / tmp_cnt;

    /* 3.wvddio = 2, mvddio=0, 比较最接近3.0v的作为wvddio */
    pmu_voltage.vddiow_lev = (abs(3000 - vddiow_3) < abs(3000 - vddiow_2)) ?  3 : 2 ;
    if (pmu_voltage.vddiow_lev == 3) {
        vddiow_val = vddiow_3;
    } else {
        vddiow_val = vddiow_2;
    }

    /* =============vddiom trim======================== */
    u8 i = 7;

    /* 1.mvddio=7, wvddio=0, 采vddio的值 */
    VDDIOM_VOL_SEL(i);
    delay32(2000);
    VDDIOW_VOL_SEL(0);
    delay32(2000);
    JL_ADC->CON |= BIT(6);
    while (!(JL_ADC->CON & BIT(7)));
    tmp_cnt = JL_ADC->RES;
    vddiom_val1 = ((u32)vbg_center * 1023) / tmp_cnt;

    /* 2.mvddio=7.6.5.4..., wvddio=0, 采vddio的值, 直到mvvdio<wvddio */
    do {
        i--;

        VDDIOM_VOL_SEL(i);
        delay32(2000);

        JL_ADC->CON |= BIT(6);
        while (!(JL_ADC->CON & BIT(7)));
        tmp_cnt = JL_ADC->RES;
        vddiom_val2 = vddiom_val1;
        vddiom_val1 = ((u32)vbg_center * 1023) / tmp_cnt;
    } while (vddiom_val1 > vddiow_val);

    /* 3.选取最接近wvddio的值作为mvddio */
    pmu_voltage.vddiom_lev = (abs(vddiom_val2 - vddiow_val) < abs(vddiom_val1 - vddiow_val)) ? i + 1 : i;
    if (pmu_voltage.vddiom_lev == (i + 1)) {
        vddiom_val = vddiom_val2;
    } else {
        vddiom_val = vddiom_val1;
    }

    if (vddiom_limit) {
        if (vddiom_val < vddiom_limit) {
            u8 j = pmu_voltage.vddiom_lev;
            vddiom_val3 = vddiom_val;

            do {
                j++;

                VDDIOM_VOL_SEL(j);
                delay32(2000);

                JL_ADC->CON |= BIT(6);
                while (!(JL_ADC->CON & BIT(7)));
                tmp_cnt = JL_ADC->RES;
                vddiom_val3 = ((u32)vbg_center * 1023) / tmp_cnt;
            } while ((vddiom_val3 < vddiom_limit) && (j <= 7));

            pmu_voltage.vddiom_lev = j;
            vddiom_val = vddiom_val3;
        }
    }

    P33_CON_SET(P3_VLVD_CON, 0, 1, old_vlvd_level);

    local_irq_enable();

    pmu_voltage.valid = 1;

    VDDIOM_VOL_SEL(pmu_voltage.vddiom_lev);

    printf("%s --- vddiom_val: %d-%d, vddiow_val: %d-%d\n", __FUNCTION__, pmu_voltage.vddiom_lev, vddiom_val, \
        pmu_voltage.vddiow_lev, vddiow_val);
}

void vddio_trim_30v(u8 tieup, u16 vddiom_limit)
{
    /* 只trim一次，从flash里面读 */
    memset_s((u8 *)&pmu_voltage, sizeof(pmu_voltage), 0, sizeof(pmu_voltage));
    int vm_len = syscfg_read(VM_PMU_VOLTAGE, (u8 *)&pmu_voltage, sizeof(pmu_voltage_type));
    if (vm_len <= 0) {
        printf("vm read pmu voltage faild\n");
        u32 tmp_vbat = 4 * get_ch_voltage(AD_CH_VBAT);
        u32 tmp_ldo5v = 4 * get_ch_voltage(AD_CH_LDO5V);
        if (((tmp_vbat >= 3700) || (tmp_ldo5v >= 3700)) && (!tieup)) {
            __vddio_trim_30V(vddiom_limit);
        } else {
            printf("record vddio voltage faild: tieup = %d || vbat = %dmv < 3700mv || ldo5v = %dmv < 3700mv\n", tieup, \
                tmp_vbat, tmp_ldo5v);
        }
        vm_len = syscfg_write(VM_PMU_VOLTAGE, (u8 *)&pmu_voltage, sizeof(pmu_voltage_type));
        if (vm_len <= 0) {
            printf("vm write pmu voltage faild\n");
        }
    } else {
        /* 正常模式下vddiow要配到最小 */
        VDDIOM_VOL_SEL(pmu_voltage.vddiom_lev);
        pmu_voltage.valid = 1;
        printf("%s --- vddiom_val: %d, vddiow_val: %d\n", __FUNCTION__, pmu_voltage.vddiom_lev, pmu_voltage.vddiow_lev);
    }
}

